#include "parafluidtrianglevoxelizer.h"

#include <QtGlobal>

using namespace ParaFluid;

void TriangleVoxelizer::ReadOnlyVisitor::visit(const Triangle * triangle)
{
	if(!(_visited.contains(triangle)))
	{
		_visited.insert(triangle);
		
		visitUnique(triangle);
	}
}

void TriangleVoxelizer::ReadOnlyVisitor::clear()
{
	_visited.clear();
}


TriangleVoxelizer::Bucket::~Bucket()
{
	/* does nothing */
}

TriangleVoxelizer::Bucket::Bucket()
	: _reservedSlotsOccupied(0)
	, _reservedSlots()
	, _extraSlots()
{
	/* does nothing */
}

TriangleVoxelizer::Bucket::Bucket(const TriangleVoxelizer::Bucket & other)
	: _reservedSlotsOccupied(other._reservedSlotsOccupied)
	, _reservedSlots()
	, _extraSlots(other._extraSlots)
{
	for(int i = 0; i < other._reservedSlotsOccupied; ++i)
	{
		_reservedSlots[i] = other._reservedSlots[i];
	}
}

TriangleVoxelizer::Bucket & TriangleVoxelizer::Bucket::operator= (const TriangleVoxelizer::Bucket & rhs)
{
	if(&rhs != this)
	{
		_reservedSlotsOccupied = rhs._reservedSlotsOccupied;
	
		for(int i = 0; i < rhs._reservedSlotsOccupied; ++i)
		{
			_reservedSlots[i] = rhs._reservedSlots[i];
		}
	
		_extraSlots = rhs._extraSlots;
	}
	return *this;
}

int TriangleVoxelizer::Bucket::occupiedSlots() const
{
	return _reservedSlotsOccupied + _extraSlots.size();
}

void TriangleVoxelizer::Bucket::append(const Triangle * triangle)
{
	if(_reservedSlotsOccupied < MIN_SLOTS_PER_BUCKET)
	{
		_reservedSlots[_reservedSlotsOccupied] = triangle;
	
		++_reservedSlotsOccupied;
	}
	else
	{
		_extraSlots.append(triangle);
	}
}

void TriangleVoxelizer::Bucket::clear()
{
	// Reset the number of Occupied Reserved Slots to Zero
	_reservedSlotsOccupied = 0;

	// Clear Extra Slots
	_extraSlots.clear();
}

void TriangleVoxelizer::Bucket::acceptVisitor(TriangleVoxelizer::ReadOnlyVisitor & visitor) const
{
	for(int i = 0; i < _reservedSlotsOccupied; ++i)
	{
		visitor.visit(_reservedSlots[i]);
	}
	for(int i = 0; i < _extraSlots.size(); ++i)
	{
		visitor.visit(_extraSlots.at(i));
	}
}

TriangleVoxelizer::~TriangleVoxelizer()
{
	/* does nothing */
}

TriangleVoxelizer::TriangleVoxelizer()
	: _smoothingDistance(1.0)
	, _buckets(100)
	, _count(0)
{
	/* does nothing */
}

TriangleVoxelizer::TriangleVoxelizer(int buckets, float smoothingDistance)
	: _smoothingDistance(smoothingDistance)
	, _buckets(buckets)
	, _count(0)
{
	/* does nothing */
}

TriangleVoxelizer::TriangleVoxelizer(const TriangleVoxelizer & other)
	: _smoothingDistance(1.0)
	, _buckets(other._buckets)
	, _count(other._count)
{
	/* does nothing */
}

TriangleVoxelizer & TriangleVoxelizer::operator= (const TriangleVoxelizer & rhs)
{
	if(&rhs != this)
	{
		_smoothingDistance = rhs._smoothingDistance;
		_buckets = rhs._buckets;
		_count = rhs._count;
	}
	return *this;
}

const float & TriangleVoxelizer::smoothingDistance() const
{
	return _smoothingDistance;
}

int TriangleVoxelizer::count() const
{
	return _count;
}

void TriangleVoxelizer::insert(const Triangle & triangle)
{
	float minX = qMin(triangle.point1().x, qMin(triangle.point2().x, triangle.point3().x));
	float minY = qMin(triangle.point1().y, qMin(triangle.point2().y, triangle.point3().y));
	float minZ = qMin(triangle.point1().z, qMin(triangle.point2().z, triangle.point3().z));
	
	float maxX = qMax(triangle.point1().x, qMax(triangle.point2().x, triangle.point3().x));
	float maxY = qMax(triangle.point1().y, qMax(triangle.point2().y, triangle.point3().y));
	float maxZ = qMax(triangle.point1().z, qMax(triangle.point2().z, triangle.point3().z));
	
	for(float x = minX; x <= maxX; x += _smoothingDistance)
	{
		for(float y = minY; y <= maxY; y += _smoothingDistance)
		{
			for(float z = minZ; z <= maxZ; z += _smoothingDistance)
			{
				_buckets[hash(voxelize(Vector(x, y, z))) % _buckets.size()].append(&triangle);
			}
		}
	}
	
	++_count;
}

void TriangleVoxelizer::clear()
{
	for(int i = 0; i < _buckets.size(); ++i)
	{
		_buckets[i].clear();
	}
	
	_count = 0;
}

void TriangleVoxelizer::acceptVisitor(ReadOnlyVisitor & visitor) const
{
	for(int i = 0; i < _buckets.size(); ++i)
	{
		_buckets[i].acceptVisitor(visitor);
	}
}

void TriangleVoxelizer::acceptVisitor(ReadOnlyVisitor & visitor, const Vector & position) const
{
	_buckets[hash(voxelize(position)) % _buckets.size()].acceptVisitor(visitor);
}

Vector TriangleVoxelizer::voxelize(const Vector & position) const
{
	Vector result(position);
	
	result.x = int(result.x / _smoothingDistance) * _smoothingDistance;
	result.y = int(result.y / _smoothingDistance) * _smoothingDistance;
	result.z = int(result.z / _smoothingDistance) * _smoothingDistance;
	
	return result;
}

unsigned int TriangleVoxelizer::hash(const Vector & voxel) const
{
	register const unsigned int prime0 = 73856093;
	register const unsigned int prime1 = 19349663;
	register const unsigned int prime2 = 83492791;
	unsigned int ux = voxel.x;
	unsigned int uy = voxel.y;
	unsigned int uz = voxel.z;
	return (ux * prime0) ^ (uy * prime1) ^ (uz * prime2);
}
